﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Aphysoft.Vate.Graphics
{
    public sealed class Projector
    {
        #region Properties
        /// <summary>
        /// Viewport.
        /// </summary>
        private Viewport viewport;
        /// <summary>
        /// Gets the viewport referenced by this instance.
        /// </summary>
        public Viewport Viewport
        {
            get { return viewport; }
            internal set { viewport = value; }
        }
        /// <summary>
        /// Projector view frustum.
        /// </summary>
        private BoundingFrustum viewFrustum;
        /// <summary>
        /// Gets view frustum.
        /// </summary>
        public BoundingFrustum ViewFrustum
        {
            get 
            {
                //if (_perf_viewFrustum_init)
                //{
                    viewFrustum = new BoundingFrustum(Matrix.Multiply(Camera.ViewMatrix, ProjectionMatrix));
                    
                    // update performance variables
                    if (_perf_viewFrustum_init) _perf_viewFrustum_init = false;
                //}

                return viewFrustum; 
            }
        }

        // Performance variables
        private bool _perf_viewFrustum_init = true;

        /// <summary>
        /// X percentage.
        /// </summary>
        private float xPercentage;
        /// <summary>
        /// Gets or sets the percentage coordinate of the upper-left corner of the projector on the render-target surface.
        /// </summary>
        public float XPercentage
        {
            get { return xPercentage; }
            set { xPercentage = value; }
        }        
        /// <summary>
        /// Y percentage.
        /// </summary>
        private float yPercentage;
        /// <summary>
        /// Gets or sets the percentage coordinate of the upper-left corner of the projector on the render-target surface.
        /// </summary>
        public float YPercentage
        {
            get { return yPercentage; }
            set { yPercentage = value; }
        }
        /// <summary>
        /// Width percentage.
        /// </summary>
        private float widthPercentage;
        /// <summary>
        /// Gets or sets the width dimension of the projector on the render-target surface, in percent.
        /// </summary>
        public float WidthPercentage
        {
            get { return widthPercentage; }
            set { widthPercentage = value; }
        }
        /// <summary>
        /// Height percentage.
        /// </summary>
        private float heightPercentage;
        /// <summary>
        /// Gets or sets the height dimension of the projector on the render-target surface, in percent.
        /// </summary>
        public float HeightPercentage
        {
            get { return heightPercentage; }
            set { heightPercentage = value; }
        }
        /// <summary>
        /// Calculated x.
        /// </summary>
        private int x;
        /// <summary>
        /// Gets the calculated pixel coordinate of the projector on th render-target surface.
        /// </summary>
        public int X
        {
            get 
            {
                if (_perf_projectionMatrix_init || _perf_x != x || _perf_xPercentage != XPercentage)
                {
                    x = (int)Math.Round((float)Vate.Width * XPercentage / 100.0f);
                    _perf_x = x;
                    _perf_xPercentage = XPercentage;
                }
                
                return x; 
            }
        }
        /// <summary>
        /// Calculated y.
        /// </summary>
        private int y;
        /// <summary>
        /// Gets the calculated pixel coordinate of the projector on th render-target surface.
        /// </summary>
        public int Y
        {
            get
            {
                if (_perf_projectionMatrix_init || _perf_y != y || _perf_yPercentage != YPercentage)
                {
                    y = (int)Math.Round((float)Vate.Height * YPercentage / 100.0f);
                    _perf_y = y;
                    _perf_yPercentage = YPercentage;
                }

                return y;
            }
        }
        /// <summary>
        /// Calculated width.
        /// </summary>
        private int width;
        /// <summary>
        /// Gets the calculated width dimension of the projector on the render-target surface.
        /// </summary>
        public int Width
        {
            get 
            {
                if (_perf_projectionMatrix_init || _perf_width != width || _perf_widthPercentage != WidthPercentage)
                {
                    width = (int)Math.Round((float)Vate.Width * WidthPercentage / 100.0f);
                    _perf_width = width;
                    _perf_widthPercentage = WidthPercentage;
                }

                return width; 
            }
        }
        /// <summary>
        /// Calculated width in half.
        /// </summary>
        private int halfWidth;
        /// <summary>
        /// Gets the calculated width dimension in half of the projector on the render-target surface.
        /// </summary>
        public int HalfWidth
        {
            get { return (int)((float)width / 2); }
        }
        /// <summary>
        /// Calculated height.
        /// </summary>
        private int height;
        /// <summary>
        /// Gets the calculated height dimension of the projector on th render-target surface.
        /// </summary>
        public int Height
        {
            get 
            {
                if (_perf_projectionMatrix_init || _perf_height != height || _perf_heightPercentage != HeightPercentage)
                {
                    height = (int)Math.Round((float)Vate.Height * HeightPercentage / 100.0f);
                    _perf_height = height;
                    _perf_heightPercentage = HeightPercentage;
                }

                return height; 
            }
        }
        /// <summary>
        /// Calculated height in half.
        /// </summary>
        private int halfHeight;
        /// <summary>
        /// Gets the calculated height dimension in half of the projector on the render-target surface.
        /// </summary>
        public int HalfHeight
        {
            get { return (int)((float)height / 2); }
        }
        /// <summary>
        /// Draw using perspective projection.
        /// </summary>
        private bool perspectiveProjection = true;
        /// <summary>
        /// Gets or sets a value indicating whether the projector being rendered using perspective projection.
        /// </summary>
        public bool PerspectiveProjection
        {
            get { return perspectiveProjection; }
            set { perspectiveProjection = value; }
        }
        /// <summary>
        /// Near plane distance.
        /// </summary>
        private float nearPlaneDistance = 0.1f;   
        /// <summary>
        /// Gets or sets near plane distance.
        /// </summary>
        public float NearPlaneDistance
        {
            get { return nearPlaneDistance; }
            set { nearPlaneDistance = value; }
        }
        /// <summary>
        /// Far plane distance.
        /// </summary>
        private float farPlaneDistance = 10000.0f;
        /// <summary>
        /// Gets or sets far plane distance.
        /// </summary>
        public float FarPlaneDistance
        {
            get { return farPlaneDistance; }
            set { farPlaneDistance = value; }
        }
        /// <summary>
        /// Field of view.
        /// </summary>
        private float fieldOfView = MathHelper.PiOver4;
        /// <summary>
        /// Gets or sets field of view.
        /// </summary>
        public float FieldOfView
        {
            get { return fieldOfView; }
            set { fieldOfView = value; }
        }
        /// <summary>
        /// Is projector will be render to the screen.
        /// </summary>
        private bool isRender = true;
        /// <summary>
        /// Gets or sets a value indicating whether this projector being rendered or not.
        /// </summary>
        public bool IsRender
        {
            get { return isRender; }
            set { isRender = value; }
        }
        /// <summary>
        /// Camera reference.
        /// </summary>
        private Camera camera = null;
        /// <summary>
        /// Gets or sets camera used by this projector.
        /// </summary>
        public Camera Camera
        {
            get { return camera; }
            set { camera = value; }
        }
        /// <summary>
        /// Projection matrix.
        /// </summary>
        private Matrix projectionMatrix = Matrix.Identity;
        /// <summary>
        /// Gets projector projection matrix.
        /// </summary>
        public Matrix ProjectionMatrix
        {
            get
            {
                if (_perf_projectionMatrix_init ||
                    _perf_x != x || _perf_xPercentage != XPercentage ||
                    _perf_y != y || _perf_yPercentage != YPercentage ||
                    _perf_widthPercentage != WidthPercentage || 
                    _perf_heightPercentage != HeightPercentage ||
                    _perf_farPlaneDistance != farPlaneDistance || _perf_nearPlaneDistance != nearPlaneDistance ||
                    _perf_fieldOfView != fieldOfView
                    )
                {
                    // Create pixel absolute
                    viewport.Width = Width;
                    viewport.Height = Height;
                    viewport.X = X;
                    viewport.Y = Y;

                    // create new projection matrix
                    float aspectRatio = (float)Width / (float)Height;

                    if (perspectiveProjection)
                    {
                        projectionMatrix = Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio, nearPlaneDistance, farPlaneDistance);
                    }
                    else
                    {
                        projectionMatrix = Matrix.CreateOrthographic(Width, Height, nearPlaneDistance, farPlaneDistance);
                    }

                    // update performance variables
                    if (_perf_projectionMatrix_init) _perf_projectionMatrix_init = false;
                }

                return projectionMatrix;
            }
        }

        // Performance variables
        private bool _perf_projectionMatrix_init = true;
        private float _perf_xPercentage = -1;
        private float _perf_yPercentage = -1;
        private float _perf_widthPercentage = -1;
        private float _perf_heightPercentage = -1;
        private int _perf_x = -1;
        private int _perf_y = -1;
        private int _perf_width = -1;
        private int _perf_height = -1;
        private float _perf_nearPlaneDistance = 1.0f;
        private float _perf_farPlaneDistance = 10000.0f;
        private float _perf_fieldOfView = MathHelper.PiOver4;

        #endregion

        #region Methods
        /// <summary>
        /// Updates current projector. Internal use only.
        /// </summary>
        public void Update()
        {
            _perf_projectionMatrix_init = true;
        }
        /// <summary>
        /// Sets camera for this projector using specified camera name in camera manager.
        /// </summary>
        /// <param name="cameraName">Camera name</param>
        public void SetCamera(string cameraName)
        {
            Camera camera = CameraManager.Find(cameraName);

            if (camera != null)
            {
                this.camera = camera;
            }
        }

        #endregion

        #region Constructor
        /// <summary>
        /// Create a new instance of game projector.
        /// </summary>
        /// <param name="xPercentage">Percentage 0-100 position X on screen</param>
        /// <param name="yPercentage">Percentage 0-100 position Y on screen</param>
        /// <param name="widthPercentage">Percentage 0-100 projector width</param>
        /// <param name="heightPercentage">Percentage 0-100 projector height</param>
        internal Projector(float xPercentage, float yPercentage, float widthPercentage, float heightPercentage)
        {
            viewport = Vate.DefaultViewport;

            this.xPercentage = xPercentage;
            this.yPercentage = yPercentage;
            this.widthPercentage = widthPercentage;
            this.heightPercentage = heightPercentage;
        }
        #endregion
    }
}
